﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class represents a polar coordinate with a radius, a polar angle and an azimuth angle.
    /// </summary>
    [Serializable]
    public class PolarCoordinate : IPolarCoordinate
    {
        /// <summary>
        /// The radius of the polar coordinate.
        /// </summary>
        private double radius;

        /// <summary>
        /// The polar angle of the polar coordinate.
        /// </summary>
        private double polarAngle;

        /// <summary>
        /// The azimuth angle of the polar coordinate.
        /// </summary>
        private double azimuthAngle;

        /// <summary>
        /// Initializes a new instance of the <see cref="PolarCoordinate"/> class.
        /// </summary>
        public PolarCoordinate()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PolarCoordinate"/> class.
        /// </summary>
        /// <param name="radius">The radius of the polar coordinate.</param>
        /// <param name="polarAngle">The polar angle of the polar coordinate.</param>
        /// <param name="azimuthAngle">The azimuth angle of the polar coordinate.</param>
        public PolarCoordinate(double radius, double polarAngle, double azimuthAngle)
        {
            this.azimuthAngle = azimuthAngle;
            this.polarAngle = polarAngle;
            this.radius = radius;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PolarCoordinate"/> class.
        /// </summary>
        /// <param name="point">The point.</param>
        public PolarCoordinate(Point3D point)
        {
            if (point == (Point3D) null)
            {
                throw new ArgumentNullException("point");
            }

            PolarCoordinate polar = PolarCoordinate.ToPolarCoordinate(point.X, point.Y, point.Z);

            this.azimuthAngle = polar.AzimuthAngle;
            this.polarAngle = polar.PolarAngle;
            this.radius = polar.Radius;
        }

        /// <summary>
        /// Gets or sets the radius.
        /// </summary>
        /// <value>The radius.</value>
        public double Radius
        {
            get { return radius; }
            set { radius = value; }
        }

        /// <summary>
        /// Gets or sets the azimuth angle in radians.
        /// </summary>
        /// <value>The azimuth angle.</value>
        public double AzimuthAngle
        {
            get { return azimuthAngle; }
            set { azimuthAngle = value; }
        }

        /// <summary>
        /// Gets or sets the polar angle in radians.
        /// </summary>
        /// <value>The polar angle.</value>
        public double PolarAngle
        {
            get { return polarAngle; }
            set { polarAngle = value; }
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static PolarCoordinate operator +(PolarCoordinate a, PolarCoordinate b)
        {
            if (a == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("b");
            }

            return
                PolarCoordinate.ToPolarCoordinate(PolarCoordinate.ToXYZCoordinate(a) +
                                                  PolarCoordinate.ToXYZCoordinate(b));
        }

        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static PolarCoordinate operator -(PolarCoordinate a, PolarCoordinate b)
        {
            if (a == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("b");
            }

            return
                PolarCoordinate.ToPolarCoordinate(PolarCoordinate.ToXYZCoordinate(a) +
                                                  PolarCoordinate.ToXYZCoordinate(b));
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(PolarCoordinate a, PolarCoordinate b)
        {
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            if (((object) a == null) || ((object) b == null))
            {
                return false;
            }

            return ((a.AzimuthAngle == b.AzimuthAngle) && (a.PolarAngle == b.PolarAngle) && (a.Radius == b.Radius));
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(PolarCoordinate a, PolarCoordinate b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Implements the operator &lt;.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator <(PolarCoordinate a, PolarCoordinate b)
        {
            if (a == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("b");
            }

            return (PolarCoordinate.ToXYZCoordinate(a) < PolarCoordinate.ToXYZCoordinate(b));
        }

        /// <summary>
        /// Implements the operator &lt;=.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator <=(PolarCoordinate a, PolarCoordinate b)
        {
            if (a == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("b");
            }

            return (PolarCoordinate.ToXYZCoordinate(a) <= PolarCoordinate.ToXYZCoordinate(b));
        }

        /// <summary>
        /// Implements the operator &gt;.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator >(PolarCoordinate a, PolarCoordinate b)
        {
            if (a == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("b");
            }

            return (PolarCoordinate.ToXYZCoordinate(a) > PolarCoordinate.ToXYZCoordinate(b));
        }

        /// <summary>
        /// Implements the operator &gt;=.
        /// </summary>
        /// <param name="a">The first coordinate.</param>
        /// <param name="b">The second coordinate.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator >=(PolarCoordinate a, PolarCoordinate b)
        {
            if (a == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("a");
            }

            if (b == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("b");
            }

            return (PolarCoordinate.ToXYZCoordinate(a) >= PolarCoordinate.ToXYZCoordinate(b));
        }

        /// <summary>
        /// Converts a point to a polar coordinate.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <returns>The converted polar coordinate.</returns>
        public static PolarCoordinate ToPolarCoordinate(Point3D point)
        {
            if (point == (Point3D) null)
            {
                throw new ArgumentNullException("point");
            }

            return PolarCoordinate.ToPolarCoordinate(point.X, point.Y, point.Z);
        }

        /// <summary>
        /// Converts a point to a polar coordinate.
        /// </summary>
        /// <param name="x">The x position of a point.</param>
        /// <param name="y">The y position of a point.</param>
        /// <param name="z">The z position of a point.</param>
        /// <returns>The converted polar coordinate.</returns>
        public static PolarCoordinate ToPolarCoordinate(double x, double y, double z)
        {
            PolarCoordinate result = new PolarCoordinate();

            result.Radius = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2) + Math.Pow(z, 2));
            result.PolarAngle = Math.PI / 2 - Math.Atan(z / (Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2))));

            if (y < 0)
            {
                result.AzimuthAngle = 2 * Math.PI - Math.Acos(x / (Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2))));
            }
            else
            {
                result.AzimuthAngle = Math.Acos(x / (Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2))));
            }

            return result;
        }

        /// <summary>
        /// Converts a polar coordinate to a point with a x, y and z element.
        /// </summary>
        /// <param name="coordinate">The coordinate.</param>
        /// <returns>A point that is convert from the specified polar coordinate.</returns>
        public static Point3D ToXYZCoordinate(PolarCoordinate coordinate)
        {
            if (coordinate == (PolarCoordinate) null)
            {
                throw new ArgumentNullException("coordinate");
            }

            Point3D result = new Point3D();

            result.X = coordinate.Radius * Math.Sin(coordinate.AzimuthAngle) * Math.Cos(coordinate.PolarAngle);
            result.Y = coordinate.Radius * Math.Sin(coordinate.AzimuthAngle) * Math.Sin(coordinate.PolarAngle);
            result.Z = coordinate.Radius * Math.Cos(coordinate.AzimuthAngle);

            return result;
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return "r = " + this.radius.ToString() + " p = " + this.polarAngle.ToString()
                   + " a = " + this.azimuthAngle.ToString();
        }
    }
}